package com.emotion.recognition.server.mlp;

import java.util.Iterator;
import java.util.List;
import java.util.Random;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

/**
 * Abstract layer.
 * 
 * @author Minsang
 */
public abstract class AbstractLayer<T extends Node> implements Layer<T> {

    private final String name;

    private InputNode biasNode;

    public AbstractLayer(String name, Double bias) {
        this.name = name;
        if (bias != null) {
            biasNode = InputNode.create();
            biasNode.setBias(bias);
            biasNode.setName(getName() + " Bias Node");
        }
    }

    @Override
    public boolean hasBiasNode() {
        return biasNode != null;
    }

    @Override
    public void fireBiasNodeIfExists() {
        if (hasBiasNode()) {
            biasNode.provideBias();
        }
    }

    @Override
    public int size() {
        return getList().size();
    }

    @Override
    public void add(T node) {
        getList().add(node);
        node.setName(getName() + " Node " + getList().size());
    }

    @Override
    public T get(int i) {
        return getList().get(i);
    }

    @Override
    public Iterator<T> iterator() {
        return getList().iterator();
    }

    protected abstract List<T> getList();

    /**
     * Connect all nodes in this layer to all nodes in the given layer.
     */
    public void connectAllTo(PerceptronLayer nextLayer) {
        Preconditions.checkNotNull(nextLayer);

        Random generator = new Random();

        for (Node current : this) {
            for (Perceptron next : nextLayer) {
                double weight = generator.nextDouble() * 0.0002 + 0.0001; // range is (0.0001,
                                                                          // 0.0003)
                weight *= generator.nextBoolean() ? -1.0 : 1.0; // flip the sign half the time
                Edge.connect(current, next, weight);
            }
        }
        if (hasBiasNode()) {
            for (Perceptron next : nextLayer) {
                double weight = generator.nextDouble() * 0.0002 + 0.0001; // range is (0.0001,
                                                                          // 0.0003)
                // Don't flip the sign for bias. Biases are usually negative.
                Edge.connect(biasNode, next, weight);
            }
        }
    }

    @Override
    public double getBias() {
        if (hasBiasNode()) {
            return biasNode.getBias();
        } else {
            return 0.0d;
        }
    }

    /**
     * Get the output from this layer, since it last fired. Should only be used for viewing the
     * previous result.
     */
    @Override
    public List<Double> getLastOuputFromAll() {
        return Lists.transform(getList(), new Function<T, Double>() {
            @Override
            public Double apply(T node) {
                return node.getLastOutput();
            }
        });
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this).add("name", getName()).add("nodes", getList())
                .toString();
    }
}
